package com.layblog.service.impl;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.layblog.entity.Post;
import com.layblog.mapper.PostMapper;
import com.layblog.service.PostService;
import com.layblog.service.UserService;
import com.layblog.utils.RedisUtil;
import com.layblog.vo.PostVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author JackLin
 * @since 2021-07-30
 */
@Slf4j
@Service
public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements PostService {

    @Autowired
    RedisUtil redisUtil;

    @Autowired
    UserService userService;

    @Autowired
    PostMapper postMapper;

    @Cacheable(cacheNames = "cache_post", key = "'post_' + #id")
    @Override
    public Post get(long id) {
        log.info("-----------> 查库了");
        Post post = this.getById(id);
        Assert.notNull(post, "该博客不存在~");
        return post;
    }

    @Override
    public boolean update(Post post) {
        log.info("-----------> 删除缓存了·");
        return updateById(post);
    }

    @Override
    public boolean delete(long id) {
        return removeById(id);
    }

    /**
     * 初始化首页的周评论排行榜
     */
    @Override
    public void initIndexWeekRank() {
        //缓存最近7天的文章评论数量
        List<Post> last7DayPosts = this.list(new QueryWrapper<Post>()
                .ge("created", DateUtil.offsetDay(new Date(), -7).toJdkDate())
                .select("id, title, user_id, comment_count, view_count, created"));

        for (Post post : last7DayPosts) {
            String key = "day_rank:" + DateUtil.format(post.getCreated(), DatePattern.PURE_DATE_PATTERN);

            //设置有效期
            long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);
            long expireTime = (7 - between) * 24 * 60 * 60;

            //缓存文章到set中，评论数量作为排行标准
            redisUtil.zSet(key, post.getId(), post.getCommentCount());
            //设置有效期
            redisUtil.expire(key, expireTime);

            //缓存文章基本信息（hash结构）
            this.hashCachePostIdAndTitle(post);
        }

        //7天阅读相加。
        this.zUnionAndStoreLast7DaysForLastWeekRank();
    }

    /**
     * hash结构缓存文章标题和id
     *
     * @param post
     */
    private void hashCachePostIdAndTitle(Post post) {

        String key = "rank_post_" + post.getId();
        redisUtil.del(key);
        boolean hasKey = redisUtil.hasKey(key);
        if (!hasKey) {
            long between = DateUtil.between(new Date(), post.getCreated(), DateUnit.DAY);
            long expireTime = (7 - between) * 24 * 60 * 60;

            //缓存文章基本信息（hash结构）
            redisUtil.hset(key, "post:id", post.getId(), expireTime);
            redisUtil.hset(key, "post:title", post.getTitle(), expireTime);
            redisUtil.hset(key, "post:commentCount", post.getCommentCount(), expireTime);
            redisUtil.hset(key, "post:viewCount", post.getViewCount(), expireTime);
        }
    }

    /**
     * 把最近7天的文章评论数量统计一下
     * 用于首页的7天评论排行榜
     */
    @Override
    public void zUnionAndStoreLast7DaysForLastWeekRank() {
        String prefix = "day_rank:";

        List<String> keys = new ArrayList<>();
        String key = prefix + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);

        for (int i = -7; i < 0; i++) {
            Date date = DateUtil.offsetDay(new Date(), i).toJdkDate();
            keys.add(prefix + DateUtil.format(date, DatePattern.PURE_DATE_PATTERN));
        }

        redisUtil.zUnionAndStore(key, keys, "last_week_rank");
    }

    /**
     * 给set里的文章评论加1，并且重新union7天的评论数量
     *
     * @param postId
     */
    @Override
    public void incrZsetValueAndUnionForLastWeekRank(Long postId, boolean isIncr) {
        String dayRank = "day_rank:" + DateUtil.format(new Date(), DatePattern.PURE_DATE_PATTERN);
        //文章阅读加一
        redisUtil.zIncrementScore(dayRank, postId, isIncr ? 1 : -1);

        this.hashCachePostIdAndTitle(this.getById(postId));

        //重新union最近7天
        this.zUnionAndStoreLast7DaysForLastWeekRank();
    }

    @Override
    @Cacheable(cacheNames = "cache_post", key = "'page_' + #page.current + '_' + #page.size " +
            "+ '_query_' +#userId  + '_' + #categoryId + '_' + #level  + '_' + #recommend  + '_' + #order")
    public IPage paging(Page page, Long userId, Long categoryId, Integer level, Boolean recommend, String order) {

        if (level == null) level = -1;

        QueryWrapper wrapper = new QueryWrapper<Post>()
                .eq(userId != null, "user_id", userId)
                .eq(categoryId != null && categoryId != 0, "category_id", categoryId)
                .gt(level > 0, "level", 0)
                .eq(level == 0, "level", 0)
                .eq(recommend != null, "recommend", recommend)
                .orderByDesc(order);

        IPage<PostVO> pageData = postMapper.selectPosts(page, wrapper);

        return pageData;
    }

    public PostVO selectOne(QueryWrapper<Post> wrapper) {
        return postMapper.selectOne(wrapper);
    }

    /**
     * hset(key,$field,$value),hset用来存储结构化数据，一个 hash存储一条数据，一个 filed则存储一条数据中的一个属性，value则是属性对应的值。
     *
     * @param post
     */
    @Override
    public void setViewCount(Post post) {
        //从缓存中获取文章的阅读数量
        Integer viewCount = (Integer) redisUtil.hget("rank_post_" + post.getId(), "post:viewCount");
        if (viewCount != null) {
            post.setViewCount((Integer) viewCount + 1);
        } else {
            post.setViewCount(post.getViewCount() + 1);
        }
        //更新新的阅读数
        redisUtil.hset("rank_post_" + post.getId(), "post:viewCount", post.getViewCount());
    }
}
